package com.gitee.wsl.transform.encode.sample

import com.gitee.wsl.ext.base.DOT
import com.gitee.wsl.ext.string.removePrefixIgnoreCase
import com.gitee.wsl.ext.string.startWithIgnoreEquals
import kotlin.jvm.JvmOverloads


/**
 * Punycode是一个根据RFC 3492标准而制定的编码系统，主要用于把域名从地方语言所采用的Unicode编码转换成为可用于DNS系统的编码
 *
 *
 * 参考：https://blog.csdn.net/a19881029/article/details/18262671
 *
 * @author looly
 * @since 5.5.2
 */
object PunyCode {
    private const val TMIN = 1
    private const val TMAX = 26
    private const val BASE = 36
    private const val INITIAL_N = 128
    private const val INITIAL_BIAS = 72
    private const val DAMP = 700
    private const val SKEW = 38
    private const val DELIMITER = '-'

    const val PUNY_CODE_PREFIX: String = "xn--"

    /**
     * punycode转码域名
     *
     * @param domain 域名
     * @return 编码后的域名
     * @throws Exception 计算异常
     */
    @Throws(Exception::class)
    fun encodeDomain(domain: String): String {
        //Assert.notNull(domain, "domain must not be null!")
        val split: List<String?> = domain.split( Char.DOT)
        val result = StringBuilder(domain.length * 4)
        for (str in split) {
            if (result.length != 0) {
                result.append(Char.DOT)
            }
            result.append(PunyCode.encode(str!!, true))
        }

        return result.toString()
    }

    /**
     * 将内容编码为PunyCode
     *
     * @param input      字符串
     * @param withPrefix 是否包含 "xn--"前缀
     * @return PunyCode字符串
     * @throws UtilException 计算异常
     */
    /**
     * 将内容编码为PunyCode
     *
     * @param input 字符串
     * @return PunyCode字符串
     * @throws UtilException 计算异常
     */
    @JvmOverloads
    @Throws(Exception::class)
    fun encode(input: CharSequence, withPrefix: Boolean = false): String {
        //Assert.notNull(input, "input must not be null!")
        var n = INITIAL_N
        var delta = 0
        var bias = INITIAL_BIAS
        val output = StringBuilder()
        // Copy all basic code points to the output
        val length = input.length
        var b = 0
        for (i in 0..<length) {
            val c = input.get(i)
            if (isBasic(c)) {
                output.append(c)
                b++
            }
        }
        // Append delimiter
        if (b > 0) {
            if (b == length) {
                // 无需要编码的字符
                return output.toString()
            }
            output.append(DELIMITER)
        }
        var h = b
        while (h < length) {
            var m = Int.Companion.MAX_VALUE
            // Find the minimum code point >= n
            for (i in 0..<length) {
                val c = input.get(i)
                if (c.code >= n && c.code < m) {
                    m = c.code
                }
            }
            if (m - n > (Int.Companion.MAX_VALUE - delta) / (h + 1)) {
                throw Exception("OVERFLOW")
            }
            delta = delta + (m - n) * (h + 1)
            n = m
            for (j in 0..<length) {
                val c = input.get(j).code
                if (c < n) {
                    delta++
                    if (0 == delta) {
                        throw Exception("OVERFLOW")
                    }
                }
                if (c == n) {
                    var q = delta
                    var k = BASE
                    while (true) {
                        val t: Int
                        if (k <= bias) {
                            t = TMIN
                        } else if (k >= bias + TMAX) {
                            t = TMAX
                        } else {
                            t = k - bias
                        }
                        if (q < t) {
                            break
                        }
                        output.append(digit2codepoint(t + (q - t) % (BASE - t)).toChar())
                        q = (q - t) / (BASE - t)
                        k += BASE
                    }
                    output.append(digit2codepoint(q).toChar())
                    bias = adapt(delta, h + 1, h == b)
                    delta = 0
                    h++
                }
            }
            delta++
            n++
        }

        if (withPrefix) {
            output.insert(0, PUNY_CODE_PREFIX)
        }
        return output.toString()
    }

    /**
     * 解码punycode域名
     *
     * @param domain PunyCode域名
     * @return 解码后的域名
     * @throws UtilException 计算异常
     */
    @Throws(Exception::class)
    fun decodeDomain(domain: String): String {

        val split = domain.split( Char.DOT)
        val result = StringBuilder(domain.length / 4 + 1)
        for (str in split) {
            if (result.length != 0) {
                result.append(Char.DOT)
            }
            result.append(if (str.startWithIgnoreEquals(PUNY_CODE_PREFIX)) PunyCode.decode(str) else str)
        }

        return result.toString()
    }

    /**
     * 解码 PunyCode为字符串
     *
     * @param input PunyCode
     * @return 字符串
     * @throws UtilException 计算异常
     */
    @Throws(Exception::class)
    fun decode(input: String): String {
        var input = input

        input = input.removePrefixIgnoreCase(PUNY_CODE_PREFIX)

        var n = INITIAL_N
        var i = 0
        var bias = INITIAL_BIAS
        val output = StringBuilder()
        var d = input.lastIndexOf(DELIMITER)
        if (d > 0) {
            for (j in 0..<d) {
                val c = input.get(j)
                if (isBasic(c)) {
                    output.append(c)
                }
            }
            d++
        } else {
            d = 0
        }
        val length = input.length
        while (d < length) {
            val oldi = i
            var w = 1
            var k = BASE
            while (true) {
                if (d == length) {
                    throw Exception("BAD_INPUT")
                }
                val c = input.get(d++).code
                val digit = codepoint2digit(c)
                if (digit > (Int.Companion.MAX_VALUE - i) / w) {
                    throw Exception("OVERFLOW")
                }
                i = i + digit * w
                val t: Int
                if (k <= bias) {
                    t = TMIN
                } else if (k >= bias + TMAX) {
                    t = TMAX
                } else {
                    t = k - bias
                }
                if (digit < t) {
                    break
                }
                w = w * (BASE - t)
                k += BASE
            }
            bias = adapt(i - oldi, output.length + 1, oldi == 0)
            if (i / (output.length + 1) > Int.Companion.MAX_VALUE - n) {
                throw Exception("OVERFLOW")
            }
            n = n + i / (output.length + 1)
            i = i % (output.length + 1)
            output.insert(i, n.toChar())
            i++
        }

        return output.toString()
    }

    private fun adapt(delta: Int, numpoints: Int, first: Boolean): Int {
        var delta = delta
        if (first) {
            delta = delta / DAMP
        } else {
            delta = delta / 2
        }
        delta = delta + (delta / numpoints)
        var k = 0
        while (delta > ((BASE - TMIN) * TMAX) / 2) {
            delta = delta / (BASE - TMIN)
            k = k + BASE
        }
        return k + ((BASE - TMIN + 1) * delta) / (delta + SKEW)
    }

    private fun isBasic(c: Char): Boolean {
        return c.code < 0x80
    }

    /**
     * 将数字转为字符，对应关系为：
     * <pre>
     * 0 -&gt; a
     * 1 -&gt; b
     * ...
     * 25 -&gt; z
     * 26 -&gt; '0'
     * ...
     * 35 -&gt; '9'
    </pre> *
     *
     * @param d 输入字符
     * @return 转换后的字符
     * @throws UtilException 无效字符
     */
    @Throws(Exception::class)
    private fun digit2codepoint(d: Int): Int {
        require(d in 0.. 35)
        return if (d < 26) {
            // 0..25 : 'a'..'z'
            d + 'a'.code
        } else if (d < 36) {
            // 26..35 : '0'..'9';
            d - 26 + '0'.code
        } else {
            throw Exception("BAD_INPUT")
        }
    }

    /**
     * 将字符转为数字，对应关系为：
     * <pre>
     * a -&gt; 0
     * b -&gt; 1
     * ...
     * z -&gt; 25
     * '0' -&gt; 26
     * ...
     * '9' -&gt; 35
    </pre> *
     *
     * @param c 输入字符
     * @return 转换后的字符
     * @throws UtilException 无效字符
     */
    @Throws(Exception::class)
    private fun codepoint2digit(c: Int): Int {
        if (c - '0'.code < 10) {
            // '0'..'9' : 26..35
            return c - '0'.code + 26
        } else if (c - 'a'.code < 26) {
            // 'a'..'z' : 0..25
            return c - 'a'.code
        } else {
            throw Exception("BAD_INPUT")
        }
    }
}
